package com.asggo.g21.service;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.BucketConfiguration;
import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy;
import io.github.bucket4j.distributed.proxy.RemoteBucketBuilder;
import io.github.bucket4j.grid.hazelcast.HazelcastProxyManager;
import io.github.bucket4j.redis.redisson.cas.RedissonBasedProxyManager;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandAsyncService;
import org.redisson.config.Config;
import org.redisson.config.ConfigSupport;
import org.redisson.connection.ConnectionManager;
import org.redisson.liveobject.core.RedissonObjectBuilder;
import org.redisson.liveobject.core.RedissonObjectBuilder.ReferenceType;
import org.springframework.stereotype.Component;

/**
 * Created by IntelliJ IDEA.
 *
 * @author eric 2024/2/19 15:46
 */
@Component
@Slf4j
public class Bucket4jService {

  private final Map<String, Bucket> cache = new ConcurrentHashMap<>();
  final RedissonClient redissonClient;

  final HazelcastInstance hazelcastInstance;

  private final RedissonBasedProxyManager<String> redissonBasedProxyManager;

  private final HazelcastProxyManager<String> hazelcastProxyManager;

  public Bucket4jService(RedissonClient redissonClient,
      HazelcastInstance hazelcastInstance) {
    this.redissonClient = redissonClient;
    this.hazelcastInstance = hazelcastInstance;
    Config config = new Config(redissonClient.getConfig());
    ConnectionManager connectionManager = ConfigSupport.createConnectionManager(config);
    RedissonObjectBuilder objectBuilder = new RedissonObjectBuilder(redissonClient);
    CommandAsyncExecutor commandExecutor = new CommandAsyncService(connectionManager, objectBuilder,
        ReferenceType.DEFAULT);

    redissonBasedProxyManager =
        RedissonBasedProxyManager.builderFor(commandExecutor)
            .withExpirationStrategy(ExpirationAfterWriteStrategy
                .basedOnTimeForRefillingBucketUpToMax(
                    Duration.ofSeconds(10))).build();

    IMap<String, byte[]> map = hazelcastInstance.getMap("bucket4j");

    hazelcastProxyManager = new HazelcastProxyManager<>(map);
  }

  public Bucket resolveBucket(String apiKey) {
    apiKey = apiKey == null ? "" : apiKey;
    return cache.computeIfAbsent(apiKey, this::newBucket);
  }

  public Bucket resolveBucketRedis(String apiKey) {
    return getBucket(apiKey, redissonBasedProxyManager.builder());
  }

  private Bucket getBucket(String apiKey, RemoteBucketBuilder<String> builder) {
    apiKey = apiKey == null ? "" : apiKey;

    return builder.build(apiKey, () -> BucketConfiguration.builder()
        .addLimit(Bandwidth.builder()
            .capacity(25L)
            .refillIntervally(5, Duration.ofSeconds(1))
            .initialTokens(0)
            .build())
        .addLimit(Bandwidth.builder()
            .capacity(100L)
            .refillIntervally(200, Duration.ofMinutes(1))
            .initialTokens(0)
            .build())
        .build());
  }

  public Bucket resolveBucketHazelcast(String apiKey) {
    return getBucket(apiKey, hazelcastProxyManager.builder());
  }

  private Bucket newBucket(String apiKey) {
    log.info("api key ->{}", apiKey);
    // 根据不同的api key 返回不同的速率限制
    return Bucket.builder()
        .addLimit(
            Bandwidth.builder()
                .capacity(100L)
                .refillIntervally(20L, Duration.ofHours(1))
                .build()
        )
        .build();
  }
}
